﻿#pragma once

#include "MathCommon.h"

#include "Vec3.h"
#include "Vec4.h"
#include "Quat.h"
#include "Matrix3x3.h"

#include "Containers/String.h"

struct WHENGINE_API SMatrix4x4
{
	Float Data[4 /*Column*/ ][4 /*Row*/ ] = {};

	static SMatrix4x4 Identity();

	SMatrix4x4()=default;

	SMatrix4x4(Float Column0[],Float Column1[],Float Column2[],Float Column3[])
	{
		Data[0][0] =Column0[0];
		Data[0][1] =Column0[1];
		Data[0][2] =Column0[2];
		Data[0][3] =Column0[3];

		Data[1][0] =Column1[0];
		Data[1][1] =Column1[1];
		Data[1][2] =Column1[2];
		Data[1][3] =Column1[3];		

		Data[2][0] =Column2[0];
		Data[2][1] =Column2[1];
		Data[2][2] =Column2[2];
		Data[2][3] =Column2[3];	

		Data[3][0] =Column3[0];
		Data[3][1] =Column3[1];
		Data[3][2] =Column3[2];
		Data[3][3] =Column3[3];			
	}

	SMatrix4x4(const SVec3& InOffset, const SQuat& InRotation, const SVec3& InScale=SVec3(1,1,1))
	{
		Data[3][0] = InOffset.X;
		Data[3][1] = InOffset.Y;
		Data[3][2] = InOffset.Z;
		Data[3][3] = 1;

		const SQuat& Q = InRotation;
		Data[0][0] = (1 - 2 * Q.Y * Q.Y - 2 * Q.Z * Q.Z); 
		Data[1][0] = 2 * Q.X * Q.Y - 2 * Q.W * Q.Z;
		Data[2][0] = 2 * Q.X * Q.Z + 2 * Q.W * Q.Y;

		Data[0][1] = 2 * Q.X * Q.Y + 2 * Q.W * Q.Z;
		Data[1][1] = (1 - 2 * Q.X * Q.X - 2 * Q.Z * Q.Z) ; 
		Data[2][1] = 2 * Q.Y * Q.Z - 2 * Q.W * Q.X;

		Data[0][2] = 2 * Q.X * Q.Z - 2 * Q.W * Q.Y;
		Data[1][2] = 2 * Q.Y * Q.Z + 2 * Q.W * Q.X;
		Data[2][2] = (1 - 2 * Q.X * Q.X - 2 * Q.Y * Q.Y);

		//scale
		Data[0][0] *= InScale.X;
		Data[0][1] *= InScale.X;
		Data[0][2] *= InScale.X;

		Data[1][0] *= InScale.Y;
		Data[1][1] *= InScale.Y;
		Data[1][2] *= InScale.Y;

		Data[2][0] *= InScale.Z;
		Data[2][1] *= InScale.Z;
		Data[2][2] *= InScale.Z;


	}

	SMatrix4x4(const SQuat& InRotation)
	{
		Data[3][0] = 0;
		Data[3][1] = 0;
		Data[3][2] =0;
		Data[3][3] = 1;

		const SQuat& Q = InRotation;
		Data[0][0] = 1 - 2 * Q.Y * Q.Y - 2 * Q.Z * Q.Z;
		Data[1][0] = 2 * Q.X * Q.Y - 2 * Q.W * Q.Z;
		Data[2][0] = 2 * Q.X * Q.Z + 2 * Q.W * Q.Y;

		Data[0][1] = 2 * Q.X * Q.Y + 2 * Q.W * Q.Z;
		Data[1][1] = 1 - 2 * Q.X * Q.X - 2 * Q.Z * Q.Z;
		Data[2][1] = 2 * Q.Y * Q.Z - 2 * Q.W * Q.X;

		Data[0][2] = 2 * Q.X * Q.Z - 2 * Q.W * Q.Y;
		Data[1][2] = 2 * Q.Y * Q.Z + 2 * Q.W * Q.X;
		Data[2][2] = 1 - 2 * Q.X * Q.X - 2 * Q.Y * Q.Y;

	}

	SMatrix4x4(const SVec3& InOffset, const SMatrix3x3& InRotation, const SVec3& InScale = SVec3(1, 1, 1))
	{
		Data[3][0] = InOffset.X;
		Data[3][1] = InOffset.Y;
		Data[3][2] = InOffset.Z;
		Data[3][3] = 1;

		Data[0][0] = InRotation.Data[0][0] * InScale.X;
		Data[1][0] = InRotation.Data[1][0] * InScale.Y;
		Data[2][0] = InRotation.Data[2][0] * InScale.Z;

		Data[0][1] = InRotation.Data[0][1] * InScale.X;
		Data[1][1] = InRotation.Data[1][1] * InScale.Y;
		Data[2][1] = InRotation.Data[2][1] * InScale.Z;

		Data[0][2] = InRotation.Data[0][2] * InScale.X;
		Data[1][2] = InRotation.Data[1][2] * InScale.Y;
		Data[2][2] = InRotation.Data[2][2] * InScale.Z;

	}

	SMatrix4x4 & operator=(const SMatrix4x4& InOther)
	{
		for (int Row = 0; Row < 4; Row++)
		{
			for (int Column = 0; Column < 4; Column++)
			{
				Data[Column][Row] = InOther.Data[Column][Row];
			}
		}
		return *this;
	}

	void SetOffset(const SVec3& InOffset)
	{
		Data[3][0] = InOffset.X;
		Data[3][1] = InOffset.Y;
		Data[3][2] = InOffset.Z;
	}

	SVec3 GetOffset() const
	{
		return SVec3(Data[3][0], Data[3][1], Data[3][2]);
	}

	void SetRotationAndScale(const SMatrix3x3& InRotationAndScale)
	{
		Data[0][0] = InRotationAndScale.Data[0][0];
		Data[1][0] = InRotationAndScale.Data[1][0];
		Data[2][0] = InRotationAndScale.Data[2][0];

		Data[0][1] = InRotationAndScale.Data[0][1];
		Data[1][1] = InRotationAndScale.Data[1][1];
		Data[2][1] = InRotationAndScale.Data[2][1];

		Data[0][2] = InRotationAndScale.Data[0][2];
		Data[1][2] = InRotationAndScale.Data[1][2];
		Data[2][2] = InRotationAndScale.Data[2][2];
	}

	void AddTransform(const SMatrix4x4& InTransform)
	{
		SMatrix4x4 CachedThis=*this;
		for (int Row = 0; Row < 3; Row++)
		{
			for (int Column = 0; Column < 4; Column++)
			{
				Data[Column][Row] = 
					  CachedThis.Data[0][Row] * InTransform.Data[Column][0]
					+ CachedThis.Data[1][Row] * InTransform.Data[Column][1]
					+ CachedThis.Data[2][Row] * InTransform.Data[Column][2];
			}
		}

	}

	//将一个点从局部坐标系转换到世界坐标系
	SVec3 TransformPosition( const SVec3& InPoint) const
	{
		SVec3 Ret;
		Ret.X = InPoint.X * Data[0][0] + InPoint.Y * Data[1][0] + InPoint.Z * Data[2][0] + Data[3][0];
		Ret.Y = InPoint.X * Data[0][1] + InPoint.Y * Data[1][1] + InPoint.Z * Data[2][1] + Data[3][1];
		Ret.Z = InPoint.X * Data[0][2] + InPoint.Y * Data[1][2] + InPoint.Z * Data[2][2] + Data[3][2];
		return Ret;
	}

	//将一个点从世界坐标系转换到局部坐标系
	SVec3 InverseTransformPosition( const SVec3& InPoint) const
	{
		return Inverse().TransformPosition(InPoint);
	}

	SMatrix4x4 operator*(const SMatrix4x4& InTransform) const
	{
		SMatrix4x4 Ret;
		for (int Row = 0; Row < 4; Row++)
		{
			for (int Column = 0; Column < 4; Column++)
			{
				Ret.Data[Column][Row] = 
				      Data[0][Row] * InTransform.Data[Column][0]
					+ Data[1][Row] * InTransform.Data[Column][1]
					+ Data[2][Row] * InTransform.Data[Column][2]
					+ Data[3][Row] * InTransform.Data[Column][3];
			}
		}
		return Ret;
	}

	CString ToString() const;

	static SMatrix4x4 LookAt(const SVec3 & InCameraPosition
			,const SVec3 & InForwardDirection
			,const SVec3& InCameraUpDirection );


	static SMatrix4x4 Perspective(Float InFovY,Float InAspect,Float InNear,Float InFar);

	static SMatrix4x4 Orthographic(Float left, Float right, Float bottom, Float top, Float zNear, Float zFar);

	//to array { C0R0,C0R1,C0R2,C0R3,C1R0,C1R1..}
	void ToFloatArray(float OutArray[16]) const;

	//glm::mat4x4 ToGlm() const;
	
	SVec4 operator*(const SVec4& InValue) const;

	SVec3 operator*(const SVec3& InValue) const;

	bool operator==(const SMatrix4x4& InOther) const;

	//获取分量
	Float Get(int64_t Column, int64_t Row) const
	{    
		return Data[Column][Row];
	}

	//逆矩阵
	SMatrix4x4 Inverse() const;

	//转置矩阵
	SMatrix4x4 Transpose() const;


};

